#@+leo-ver=4
#@+node:@file DiffTeXCore2.0.py
#@@language python
#@<<declarations>>
#@+node:<<declarations>>
#!/usr/bin/python2.2
"""LaTeX Diff core compents"""

__author__ = 'Ian Mulvany <ian@mulvany.net>'
__copyright__ = '(C) 2003 Ian Mulvany. GNU GPL 2.'
__version__ = '0.2'

import difflib
import string
import os
import copy
import re

#workingDir = "//Users//ianmulvany//code//python//MPython//springer//manuscriptParser//"
workingDir = "e:\Dokumente und Einstellungen\Mulvany\Eigene Dateien\L_Leos\\parserInput\\"

#@-node:<<declarations>>
#@nl
#@+others
#@+node:checkFences
"""
check that {,( and [ are balanced while ignoring 
any \{ etc.
"""

def checkFences(testString):
	skip = 0
	 
	curlyLeft = 0
	curlyRight = 0
	curlyEarly = 0
	squareLeft = 0
	squareRight = 0
	squareEarly = 0
	parenLeft = 0
	parenRight = 0
	parenEarly = 0
	
	for char in testString:
		if skip == 1:
			skip = 0
			continue
		elif char == "\\":
			skip = 1
			continue
		elif char == '{':
			curlyLeft += 1
		elif char == '}':
			curlyRight += 1
		elif char == '[':
			squareLeft += 1
		elif char == ']':
			squareRight += 1
		elif char == '(':
			parenLeft += 1
		elif char == ')':
			parenRight += 1
		else:
			continue
		
		if curlyLeft - curlyRight < 0:
			curlyEarly += 1
			
		if squareLeft - squareRight < 0:
			squareEarly += 1
			
		if parenLeft - parenRight < 0:
			parenEarly += 1
			
 
	if curlyLeft - curlyRight != 0:
		print " curly brackets are unbalenced "
			
	if squareLeft - squareRight != 0:
		print " square brackets are unbalenced "
		
	if parenLeft - parenRight != 0:
		print " parenntheses are unbalenced "

	if curlyEarly > 0:
		print " a closing curly bracket apprears before an opening one "	

	if squareEarly > 0:
		print " a closing square bracket apprears before an opening one "	

	if parenEarly > 0:
		print " a closing parenthesis apprears before an opening one "	
			
#@nonl
#@-node:checkFences
#@+node:getIFileNames
"""
This module will be replaced by a GUI later, perhaps early next week.
"""
def getFileNames(workingDir):
	
	fileList = os.listdir(workingDir)

	print '=' * 15
	print "here is the list of files to choose from"

	for x in range(len(fileList)):
		print x, '\t : \t' , fileList[x]
	print ''
			
	index1 = int(raw_input("please enter the number for the first file   :"))
	index2 = int(raw_input("please enter the number for the second file  :"))
	
	a = workingDir + fileList[index1]
	b = workingDir + fileList[index2]
	c = workingDir + "diffFile.tex"

	return a,b,c


	
#@-node:getIFileNames
#@+node:readFileLines
def readFileLines(filename):
	return open(filename,'U').readlines()
#@nonl
#@-node:readFileLines
#@+node:preprocessFileLines1
#@+others
#@+node:doRexp
"""
This is a general regular expression module,
we pass the patterns and the line to the module

([ \t\r\f\v]+)		any whitespace that does not include a newline character! 
"""
def doRexp(pattern, replace, line):
	if re.search(pattern, line):
		out = re.sub(pattern, replace, line)
		return out
	else: 
		return line
#@nonl
#@-node:doRexp
#@+node:stripComment
"""
as argument takes a line
returns a line that does not have any comments
needs to ignore \% !
some \% and % the rest is a comment
"""
def stripComment(line):
	if line[0] == "%":
		return ' '
	elif "%" in line:
		laschar = ''
		for char in line:
			if char != "%":
				lastchar = char
				continue
			elif lastchar != '\\':
				code, comment = line.split("%",1)
				return code
			else:
				lastchar = char
				continue
	else:
		return line
#@-node:stripComment
#@+node:stripSafeNewLines
"""
Now we are looking at lines from a file that has been passed to us
from readlines() which means that the last character of every line is
garanteed to be a newline character. 

This makes detterming whether we should chop it or not very easy
"""
def stripSafeNewLines(line):
	#print len(line)
	#print line[-1]
	# if the line is only a newline character
	if len(line) < 2:
		return line
	# if the newline comes after a 'special' TeX command
	elif (line[-2] == '}') or (line[-2] == '\\') :
		return line
	# otherwise strip it	
	else: 
		# would be neater to use the re module here, but this works for now
		out = string.rstrip(line)
		out = out + ' '
		return out
#@-node:stripSafeNewLines
#@-others

def preprocessFileLines1(fileLines):
	
	"""
	THINK VERY CAEFULLY ABOUT THESE REGEXPS THINK THINK
	
	things to consider+
	
	some change

	need to remove spaces from \begin{ command } and \end{ command } structures
	need to ensure that \Add and \Del{} commands do not contain curly brackets
	or other special characters.
	
	if an _ or a ^ are not followed by encasing brackets, then place encasing brackets areound their 
	arguments. Do this before we add out magi spaces.
	"""
	
	procList = []
	#
	# set up our 're' patterns that will be used in some of our functions in this module
	# many of these regex's were taken straigt from the program by Dekel Tsur.
	#
	# for cleaning up math modes
	underscore = r"(?<!(\\))(_(([[(?*.+!\\\w]*)(\s|\$)))"
	underscoreReplace = "^{\\4}\\5"
	power = r"(?<!(\\))(\^(([[(?*.+!\\\w]*)(\s|\$)))"
	powerReplace = "^{\\4}\\5"
	## any whitespace that does not include a newline character! 
	multiSpace = r"([ \t\r\f\v]+)"		
	space = " "
	#
	# }'s
	rCurly = r"(?<!\\)(})"
	rCurlyBlank = r" } "
	#
	# {'s
	lCurly = r"{"
	lBlankCurly = r"{ "
	#
	# both { and }, not currently used
	Curly = r"(?<!\\)(\{|\})"
	#
	# change totally blank lines to only a newline character
	balnkLine = r"^[\s]*(?![\s*\S])"
	newLine = r"\n"
	#
	# \command{text
	TeXCommand = r"(\\(\w+){)"
	#
	# a $ that does not have '\' or '$' in front and '$' behind 
	singleDollar = r"(?<!(\\|\$))(\$)(?!\$)"
	singleDollarSpace = r" $ "
	#
	# $$ without "\" in front
	mathmode = r"(?<!\\)(\$\$)"
	#
	# item
	item = r"(\\(item))"	
	#
	# ( and )
	Parenthesis = r"(\(|\)|\(|\)|\[|\])"
	#
	# remove some spaces that could cause problems
	badSpaces = r"\\(begin|end|label|ref|cite|text|textrm|mbox|usepackage|thispagestyle){ (\w+)\*? }" 
	badSpacesReplace = r"\\\1{\2}"
	#
	# some spaces to place place as replacements
	newlineSub = "%\n\\1"
	newlineMathSub = "%\n \\1 "
	subSpace = r" \1 "


	for line in fileLines:
		# remove comments
		#print [line]
		#curLine = stripComment(line)
		#
		curLine = line
		# strip whitespace off the start of the line
		if curLine != None:
			curLine = string.lstrip(curLine)
		
		# strip 'safe' newline characters from the line
		curLine = stripSafeNewLines(curLine)
		#
		# replace a line(or multiple lines) that is(are) only whitespace with a newline
		curLine = doRexp(balnkLine,newLine,curLine)
		#
		# replace multiple spaces that do not have a newline character with one space
		curLine = doRexp(multiSpace,space,curLine)	
		#
		curLine = doRexp(underscore,underscoreReplace,curLine)
		curLine = doRexp(power,powerReplace,curLine)
		# add special padding around curly brackets, and perhaps other objects too
		#curLine = doRexp(Curly,subSpace,curLine)
		curLine = doRexp(rCurly,rCurlyBlank,curLine)
		curLine = doRexp(lCurly,lBlankCurly,curLine)
		curLine = doRexp(Parenthesis,subSpace,curLine)
		#
		# now add a newline before commands
		curLine = doRexp(TeXCommand,newlineSub,curLine)
		curLine = doRexp(item,newlineSub,curLine)	
		#
		# some more maths, dealing with single dollar signs and double dollar sighns
		curLine = doRexp(mathmode,newlineMathSub,curLine)	
		curLine = doRexp(singleDollar,singleDollarSpace,curLine)	
		#
		# take out some spaces that we put in that cause problems
		curLine = doRexp(badSpaces,badSpacesReplace,curLine)	
		
		#
		procList.append(curLine)
	return procList
#@-node:preprocessFileLines1
#@+node:joinLines2text
"""
Lines is a list of lines, actually any list
joiner is a string that we use as glue between the items in the list
"""
def joinLines2text(Lines,joiner):
	return joiner.join(Lines)
#@nonl
#@-node:joinLines2text
#@+node:tex2list
"""
This function will become my tex parser, oh yes it will.

This stripped down parser just splits our input file (a full text string) into a 
list sequence broken at whitespace whiltespace.

we might want to handle whitespace a bit more intelligently.
"""
def tex2list(x):
	cur = ''
	out = []
	for c in x:
		if c in string.whitespace: out.append(cur+c); cur = ''
		else: cur += c
	out.append(cur)
	return filter(lambda x: x is not '', out)
#@-node:tex2list
#@+node:texDiff
def texDiff(a, b):
	"""Takes in lists a and b and returns a human-readable LaTeX diff. Perhaps"""
	print 'in TeX Diff'
	textout = []
	
	diffout = []
	#a, b = tex2list(a), tex2list(b)
	s = difflib.SequenceMatcher(None, a, b)
	for e in s.get_opcodes():
		if e[0] == "replace":
			# @@ need to do something more complicated here
			# call textDiff but not for html, but for some html... ugh
			# gonna cop-out for now
			print 'deleting ', ''.join(a[e[1]:e[2]]), 'and adding ', ''.join(b[e[3]:e[4]])
			textout.append('\Del{'+''.join(a[e[1]:e[2]]) + '}\Add{'+''.join(b[e[3]:e[4]])+"}")
		elif e[0] == "delete":
			print 'deleting ', ''.join(a[e[1]:e[2]])
			textout.append('\Del{'+ ''.join(a[e[1]:e[2]]) + "}")
		elif e[0] == "insert":
			print 'adding ', ''.join(b[e[3]:e[4]])
			textout.append('\Add{'+''.join(b[e[3]:e[4]]) + "}")
		elif e[0] == "equal":
			textout.append(''.join(b[e[3]:e[4]]))
		else: 
			raise "Um, something's broken. I didn't expect a '" + `e[0]` + "'."
	diffRatio = s.ratio()
	textout.append("\n the diff ratio of this file is %f\n" % (diffRatio))
	return  textout #''.join(out)

#@-node:texDiff
#@+node:texDifforiginal
def texDifforiginal(a, b):
	"""Takes in lists a and b and returns a human-readable LaTeX diff. Perhaps"""

	out = []
	#a, b = tex2list(a), tex2list(b)
	s = difflib.SequenceMatcher(None, a, b)
	for e in s.get_opcodes():
		if e[0] == "replace":
			# @@ need to do something more complicated here
			# call textDiff but not for html, but for some html... ugh
			# gonna cop-out for now
			out.append('\Del{'+''.join(a[e[1]:e[2]]) + '}\Add{'+''.join(b[e[3]:e[4]])+"}")
		elif e[0] == "delete":
			out.append('\Del{'+ ''.join(a[e[1]:e[2]]) + "}")
		elif e[0] == "insert":
			out.append('\Add{'+''.join(b[e[3]:e[4]]) + "}")
		elif e[0] == "equal":
			out.append(''.join(b[e[3]:e[4]]))
		else: 
			raise "Um, something's broken. I didn't expect a '" + `e[0]` + "'."
	diffRatio = s.ratio()
	out.append("\n the diff ratio of this file is %f\n" % (diffRatio))
	return  out #''.join(out)
#@-node:texDifforiginal
#@+node:manageFile
"""
since we do the same thing repetedly to
two different file, tidy things up and put those
functions in here.

PP in the namespace here stands for Post Process
"""
def manageFile(filename):
	# read the file
	fileLines = readFileLines(filename)
	print '-'*12
	print  'readFileLines :', [fileLines[-1][-10:]]
	print  "there are" , len(fileLines) , "lines in this input file"
	print '-'*12
	# pre process the file
	fileLinesPP = preprocessFileLines1(fileLines)
	print '-'*12
	print  'preprocessFileLines :', [fileLinesPP[-1]]
	print '-'*12
	# create a text stream
	textPP = joinLines2text(fileLinesPP,'')
	# create a list broken by whitespace
	ListPP = tex2list(textPP)
	print '-'*12
	print 'tex2list:', [ListPP[-1]]
	print '-'*12
	# get rid of multiple new lines?
	return ListPP
#@nonl
#@-node:manageFile
#@-others
# read in the file names
a,b,c = getFileNames(workingDir)

# read the files
# preprocess the files
# join the files
# make wspc seperated lists from the files
aListPP = manageFile(a)
bListPP = manageFile(b)

#print aListPP[-10:]
DiffList = texDiff(aListPP,bListPP)
DiffText = joinLines2text(DiffList,'')

inputFile = open(a,'U').read()
print "checking input file"
checkFences(inputFile)

print "checking Diff file"
checkFences(DiffText)

open(c,'w').write(DiffText)
print "script finished"




#@-node:@file DiffTeXCore2.0.py
#@-leo
